var PL = {
    NV_P_FORMAT: 'p_format=json',
    NV_P_MODE: 'p_mode=pull',
    pushletURL: null,
    webRoot: null,
    sessionId: null,
    STATE_ERROR: -2,
    STATE_ABORT: -1,
    STATE_NULL: 1,
    STATE_READY: 2,
    STATE_JOINED: 3,
    STATE_LISTENING: 4,
    state: 1,
    // Cross-browser add event listener to element
    _addEvent: function (elm, evType, callback, useCapture) {
        var obj = PL._getObject(elm);
        if (obj.addEventListener) {
            obj.addEventListener(evType, callback, useCapture);
            return true;
        } else if (obj.attachEvent) {
            var r = obj.attachEvent('on' + evType, callback);
            return r;
        } else {
            obj['on' + evType] = callback;
        }
    },
    _doCallback: function (event, cbFunction) {
        if (cbFunction) {
            cbFunction(event);
        } else if (window.onEvent) {
            onEvent(event);
        }
    },
    // Do HTTP request
    _doRequest: function (anEvent, aQuery) {
        if (PL.state < PL.STATE_ABORT) { // abort时仍尝试发起请求
            PL._setStatus('died (' + PL.state + ')');
            return;
        }
        // We may have (async) requests outstanding and thus
        // may have to wait for them to complete and change state.
        var waitForState = false;
        if (anEvent == 'join' || anEvent == 'join-listen') {
            // We can only join after initialization
            waitForState = (PL.state < PL.STATE_READY);
        } else if (anEvent == 'leave') {
            PL.state = PL.STATE_READY;
        } else if (anEvent == 'refresh') {
            // We must be in the listening state 或者 abort，尝试在集群的其他服务器上再订阅
            if (PL.state != PL.STATE_LISTENING && PL.state != PL.STATE_ABORT) {
                return;
            } else if (PL.state == PL.STATE_ABORT) {
                anEvent = "reconnect"
            }
        } else if (anEvent == 'listen') {
            // We must have joined before we can listen
            waitForState = (PL.state < PL.STATE_JOINED);
        } else if (anEvent == 'subscribe' || anEvent == 'unsubscribe') {
            // We must be listeing for subscription mgmnt
            waitForState = (PL.state < PL.STATE_LISTENING);
        } else {
            // All other requests require that we have at least joined
            waitForState = (PL.state < PL.STATE_JOINED);
        }
        // May have to wait for right state to issue request
        if (waitForState == true) {
            PL._setStatus(anEvent + ' , waiting... state=' + PL.state);
            setTimeout(function () {
                PL._doRequest(anEvent, aQuery);
            }, 100);
            return;
        }
        // ASSERTION: PL.state is OK for this request
        // Construct base URL for GET
        var url = PL.pushletURL + '?p_event=' + anEvent;
        // Optionally attach query string
        aQuery && (url +='&' + aQuery);
        // Optionally attach session id
        if (PL.sessionId != null) {
            url = url + '&p_id=' + PL.sessionId;
            if (anEvent == 'p_leave') {
                PL.sessionId = null;
            }
        }
        PL.debug('_doRequest', url);
        PL._getJSON(url, PL._onResponse);
    },
    // Get object reference
    _getObject: function (obj) {
        if (typeof obj == "string") {
            return document.getElementById(obj);
        } else {
            // pass through object reference
            return obj;
        }
    },
    _getWebRoot: function () {
        if (PL.webRoot != null) {
            return PL.webRoot;
        }
        //derive the baseDir value by looking for the script tag that loaded this file
        var head = document.getElementsByTagName('head')[0];
        var nodes = head.childNodes;
        for (var i = 0; i < nodes.length; ++i) {
            var src = nodes.item(i).src;
            if (src) {
                var index = src.indexOf("json-pushlet-client.js");
                if (index >= 0) {
                    index = src.indexOf("lib");
                    PL.webRoot = src.substring(0, index);
                    break;
                }
            }
        }
        return PL.webRoot;
    },
    // 从服务器端获取JSON数据
    _getJSON: function (requestURL, callback) {
        var isAsync = false;
        callback && (isAsync = true);
        $.ajax({
            async: isAsync,
            dataType: 'json',
            type: 'GET',
            url: requestURL,
            success:function(data, textStatus, jqXHR) {
                if (jqXHR.readyState == 4) {
                    if (jqXHR.status == 200) {
                        if (callback) {
                            callback(data);
                        } else {
                            jqXHR = null;
                            return data;
                        }
                    } else {
                        PL._setStatus('error for reason: '+ textStatus +', refresh again after 1s.'); // 异常的response，可能是服务器异常退出导致
                        setTimeout(function () {PL._getJSON(requestURL, callback);}, 1500); // 尝试1.5秒后重新请求
                        if (!callback) {
                            jqXHR = null;
                            return null;
                        }
                    }
                }
                jqXHR = null;
            },
            error: function(jqXHR, textStatus, errorThrown) {
                PL._setStatus('error for reason: '+ textStatus +', refresh again after 1s.');
                setTimeout(function () {PL._getJSON(requestURL, callback);}, 1500);
                jqXHR = null;
                if (!callback)
                    return null;
            }
        });
    },
    _init: function () {
        PL._showStatus();
        PL._setStatus('initializing...');
        PL.pushletURL = PL._getWebRoot() + 'pushlet.srv';
        PL._setStatus('initialized');
        PL.state = PL.STATE_READY;
    },
    /** Handle incoming events from server. */
    _onEvent: function (event) {
        PL.debug('_onEvent()', event);
        var eventType = event.p_event
        if (eventType == 'data') {
            PL._setStatus('data');
            var _subject = event.p_subject;
            // 需要注意subject的命名，回调函数用subject名命名
            PL._doCallback(event, window[_subject]);
        } else if (eventType == 'refresh') {
            if (PL.state < PL.STATE_LISTENING) {
                PL._setStatus('not refreshing state=' + PL.STATE_LISTENING);
            }
            var timeout = event.p_wait;
            setTimeout(function () {
                PL._doRequest('refresh');
            }, timeout);
            return;
        } else if (eventType == 'error') {
            PL.state = PL.STATE_ERROR;
            PL._setStatus('server error: ' + event.p_reason);
            PL._doCallback(event, window.onError);
        } else if (eventType == 'join-ack') {
            PL.state = PL.STATE_JOINED;
            PL.sessionId = event.p_id;
            PL._setStatus('connected');
            PL._doCallback(event, window.onJoinAck);
        } else if (eventType == 'join-listen-ack') {
            PL.state = PL.STATE_LISTENING;
            PL.sessionId = event.p_id;
            PL._setStatus('join-listen-ack');
            PL._doCallback(event, window.onJoinListenAck);
        } else if (eventType == 'listen-ack') {
            PL.state = PL.STATE_LISTENING;
            PL._setStatus('listening');
            PL._doCallback(event, window.onListenAck);
        } else if (eventType == 'hb') {
            PL._setStatus('heartbeat');
            PL._doCallback(event, window.onHeartbeat);
        } else if (eventType == 'hb-ack') {
            PL._doCallback(event, window.onHeartbeatAck);
        } else if (eventType == 'leave-ack') {
            PL._setStatus('disconnected');
            PL._doCallback(event, window.onLeaveAck);
        } else if (eventType == 'refresh-ack') {
            PL._doCallback(event, window.onRefreshAck);
        } else if (eventType == 'subscribe-ack') {
            PL._setStatus('subscribed to ' + event.p_subject);
            PL._doCallback(event, window.onSubscribeAck);
        } else if (eventType == 'unsubscribe-ack') {
            PL._setStatus('unsubscribed');
            PL._doCallback(event, window.onUnsubscribeAck);
        } else if (eventType == 'abort') {
            PL.state = PL.STATE_ABORT; // abort事件，可能是集群某台服务器正常关闭，尝试继续发送请求
            PL._setStatus('abort');
            PL._doCallback(event, window.onAbort);
        } else if (eventType == "reconnect-ack") {
            // 重新订阅
            PL.state = PL.STATE_LISTENING;
            PL.sessionId = event.p_id;
            PL._setStatus('reconnect');
            PL._doCallback(event, window.onReConnectAck);
        } else if (eventType.match(/nack$/)) {
            PL._setStatus('error response: ' + event.p_reason);
            if (window.onNack) {
            	PL._doCallback(event, window.onNack);
            } else {
            	alert('error response: ' + event.p_reason);
            }
        }
    },
    /**  处理返回的JSON数据. */
    _onResponse: function (json) {
        PL.debug('_onResponse', json);
        PL.debug('_onResponse eventCnt=', json.length);
        for (var i = 0; i < json.length; i++) {
            PL._onEvent(json[i]);
        }
        delete json;
    },
    statusMsg: 'null',
    statusChanged: false,
    statusChar: '|',
    _showStatus: function () {
        if (PL.statusChanged == true) {
            if (PL.statusChar == '|') PL.statusChar = '/';
            else if (PL.statusChar == '/') PL.statusChar = '--';
            else if (PL.statusChar == '--') PL.statusChar = '\\';
            else PL.statusChar = '|';
            PL.statusChanged = false;
        }
        window.defaultStatus = PL.statusMsg;
        window.status = PL.statusMsg + '  ' + PL.statusChar;
        var timeout = window.setTimeout('PL._showStatus()', 400);
    },
    _setStatus: function (status) {
        PL.statusMsg = "pushlet - " + status;
        PL.statusChanged = true;
    },
    /*************** Debug utility *******************************/
    timestamp: 0,
    debugWindow: null,
    messages: [],
    messagesIndex: 0,
    debugOn: false,
    debug: function (label, value) {
        if (PL.debugOn == false) {
            return;
        }
        var funcName = "none";
        if (PL.debug.caller) {
            funcName = PL.debug.caller.toString()
            funcName = funcName.substring(9, funcName.indexOf(")") + 1)
        }
        var msg = "-" + funcName + ": " + label + "=" + JSON.stringify(value);
        var now = new Date();
        var elapsed = now - PL.timestamp;
        if (elapsed < 10000) {
            msg += " (" + elapsed + " msec)"
        }
        PL.timestamp = now;
        if ((PL.debugWindow == null) || PL.debugWindow.closed) {
            PL.debugWindow = window.open("", "p_debugWin", "toolbar=no,scrollbars=yes,resizable=yes,width=600,height=400");
        }
        // Add message to current list
        PL.messages[PL.messagesIndex++] = msg;
        // Write doc header
        PL.debugWindow.document.writeln('<html><head><title>Pushlet Debug Window</title></head><body bgcolor=#DDDDDD>');
        // Write the messages
        for (var i = 0; i < PL.messagesIndex; i++) {
            PL.debugWindow.document.writeln('<pre>' + i + ': ' + PL.messages[i] + '</pre>');
        }
        // Write doc footer and close
        PL.debugWindow.document.writeln('</body></html>');
        PL.debugWindow.document.close();
        PL.debugWindow.focus();
    }
}
// Initialize when page completely loaded
PL._addEvent(window, 'load', PL._init, false);